define(function(require, exports, module) {

    var $ = require('jquery'),
        Position = require('position'),
        Shim = require('iframe-shim'),
        Widget = require('widget');


    // Overlay
    // -------
    // Overlay 组件的核心特点是可定位（Positionable）和可层叠（Stackable）
    // 是一切悬浮类 UI 组件的基类

    var Overlay = Widget.extend({

        attrs: {
            // 基本属性
            width: null,
            height: null,
            zIndex: 99,
            visible: false,

            // 定位配置
            align: {
                // element 的定位点，默认为左上角
                selfXY: [0, 0],
                // 基准定位元素，默认为当前可视区域
                baseElement: Position.VIEWPORT,
                // 基准定位元素的定位点，默认为左上角
                baseXY: [0, 0]
            },

            // 父元素
            parentNode: document.body
        },

        show: function() {
            // 若从未渲染，则调用 render
            if (!this.rendered) {
                this.render();
            }
            this.set('visible', true);
            return this;
        },

        hide: function() {
            this.set('visible', false);
            return this;
        },

        setup: function() {
            var that = this;
            // 加载 iframe 遮罩层并与 overlay 保持同步
            this._setupShim();
            // 窗口resize时，重新定位浮层
            this._setupResize();
            // 统一在显示之后重新设定位置
            this.after('show', function() {
                that._setPosition();
            });
        },

        destroy: function() {
            // 销毁两个静态数组中的实例
            erase(this, Overlay.allOverlays);
            erase(this, Overlay.blurOverlays);
            return Overlay.superclass.destroy.call(this);
        },

        // 进行定位
        _setPosition: function(align) {
            // 不在文档流中，定位无效
            if (!isInDocument(this.element[0])) return;

            align || (align = this.get('align'));
            
            // 如果align为空，表示不需要使用js对齐
            if(!align) return;
            
            var isHidden = this.element.css('display') === 'none';

            // 在定位时，为避免元素高度不定，先显示出来
            if (isHidden) {
                this.element.css({ visibility: 'hidden', display: 'block' });
            }

            Position.pin({
                element: this.element,
                x: align.selfXY[0],
                y: align.selfXY[1]
            }, {
                element: align.baseElement,
                x: align.baseXY[0],
                y: align.baseXY[1]
            });

            // 定位完成后，还原
            if (isHidden) {
                this.element.css({ visibility: '', display: 'none' });
            }

            return this;
        },

        // 加载 iframe 遮罩层并与 overlay 保持同步
        _setupShim: function() {
            var shim = new Shim(this.element);

            // 在隐藏和设置位置后，要重新定位
            // 显示后会设置位置，所以不用绑定 shim.sync
            this.after('hide _setPosition', shim.sync, shim);  

            // 除了 parentNode 之外的其他属性发生变化时，都触发 shim 同步
            var attrs = ['width', 'height'];
            for (var attr in attrs) {
                if (attrs.hasOwnProperty(attr)) {
                    this.on('change:' + attr, shim.sync, shim);
                }
            }
            
            // 在销魂自身前要销毁 shim
            this.before('destroy', shim.destroy, shim);
        },

        // resize窗口时重新定位浮层，用这个方法收集所有浮层实例
        _setupResize: function() {
            Overlay.allOverlays.push(this);
        },
        
        // 除了 element 和 relativeElements，点击 body 后都会隐藏 element
        _blurHide: function(arr) {
            arr = $.makeArray(arr);
            arr.push(this.element);
            this._relativeElements = arr;
            Overlay.blurOverlays.push(this);
        },

        // 用于 set 属性后的界面更新

        _onRenderWidth: function(val) {
            this.element.css('width', val);
        },

        _onRenderHeight: function(val) {
            this.element.css('height', val);
        },

        _onRenderZIndex: function(val) {
            this.element.css('zIndex', val);
        },

        _onRenderAlign: function(val) {
            this._setPosition(val);
        },

        _onRenderVisible: function(val) {
            this.element[val ? 'show' : 'hide']();
        }

    });

    // 绑定 blur 隐藏事件
    Overlay.blurOverlays = [];
    $(document).on('click', function(e) {
        hideBlurOverlays(e);
    });

    // 绑定 resize 重新定位事件
    var timeout;
    var winWidth = $(window).width(), winHeight = $(window).height();
    Overlay.allOverlays = [];
    $(window).resize(function() {
        var winNewWidth = $(window).width(), winNewHeight = $(window).height();
        // IE678 莫名其妙触发 resize 
        // http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer
        if (winWidth !== winNewWidth || winHeight !== winNewHeight) {
            timeout && clearTimeout(timeout);
            timeout = setTimeout(function() {
                $(Overlay.allOverlays).each(function(i, item) {
                    // 当实例为空或隐藏时，不处理
                    if(!item || !item.get('visible')) {
                        return;
                    }
                    item._setPosition();
                });
            }, 80);
        }
        winWidth = winNewWidth;
        winHeight = winNewHeight;
    });

    module.exports = Overlay;


    // Helpers
    // -------

    function isInDocument(element) {
        return $.contains(document.documentElement, element);
    }

    function hideBlurOverlays(e) {
        $(Overlay.blurOverlays).each(function(index, item) {
            // 当实例为空或隐藏时，不处理
            if(!item || !item.get('visible')) {
                return;
            }
            
            // 遍历 _relativeElements ，当点击的元素落在这些元素上时，不处理
            for(var i=0; i<item._relativeElements.length; i++) {
                var el = $(item._relativeElements[i])[0];
                if (el === e.target || $.contains(el, e.target)) {
                    return;
                }
            }

            // 到这里，判断触发了元素的 blur 事件，隐藏元素
            item.hide();
        });
    }

    // 从数组中删除对应元素
    function erase(target, array) {
        for(var i=0; i<array.length; i++) {
            if (target === array[i]) {
                array.splice(i, 1);
                return array;
            }
        }
    }

});

